﻿using System;
using System.Diagnostics;
using System.IO.Compression;
using System.Runtime.InteropServices;

namespace CSharpExt.IO.Compression
{

    // ======================================================================
    // z_stream
    // ======================================================================

    /// <summary>
    /// Questa struttura dati ha la stessa forma di quella utilizzata dalla
    /// libreria "zlib" (http://www.zlib.net/).
    /// 
    /// E' stata riprodotta qui per semplificare l'utilizzo delle funzioni
    /// a basso livello (deflate???, inflate???) messe a disposizione.
    /// 
    /// ATTENZIONE:
    /// - la libreria e' scritta in C e quindi le dichiarazioni delle strutture dati
    ///   sono state opportunamente convertite per assicurarsi che abbiano la stessa
    ///   dimensione anche in .NET
    /// - a seconda della piattaforma, i putatori (byte*) possono essere a 32 o 64bit!!!!
    /// - benche' in "zlib.h" alcuni membri della struttura siano dichiarati uLong, 
    ///   (unsigned long) sulle piattaforme a 32 e 64bit un "long" ha la stessa 
    ///   dimensione di un "int" e cioe' 32bit
    /// - in .NET un "long" e' un intero a 64bit!!!
    /// 
    /// Inoltre a
    /// </summary>
    unsafe struct z_stream
    {
        public byte* next_in;
        public int/*uInt*/  avail_in;
        public int/*uLong*/ total_in;

        public byte* next_out;
        public int/*uInt*/  avail_out;
        public int/*uLong*/ total_out;

        public byte* msg;       // non sono interessante
        public byte* state;     // non sono interessante
        public byte* zalloc;    // non sono interessante
        public byte* zfree;     // non sono interessante
        public byte* opaque;    // non sono interessante

        public int data_type;
        public int/*uLong*/ adler;
        public int/*uLong*/ reserved;
    };

    // ======================================================================
    // zlib
    // ======================================================================

    /// <summary>
    /// Questa classe ha il solo compito di mappare in ambiente .NET le API 
    /// messe a disposizione dalla libreria binaria "zlib1.dll" (nella sua
    /// versione a 32 o 64 bit)
    /// 
    /// deflate -> zip
    /// inflate -> unzip
    /// 
    /// </summary>
    internal static unsafe class zlib
    {
        #region z_stream Constants
        // ==================================================================
        // z_stream Constants
        // ==================================================================

        public static readonly int Z_NO_FLUSH = 0;
        public static readonly int Z_PARTIAL_FLUSH = 1; /* will be removed, use Z_SYNC_FLUSH instead */
        public static readonly int Z_SYNC_FLUSH = 2;
        public static readonly int Z_FULL_FLUSH = 3;
        public static readonly int Z_FINISH = 4;
        public static readonly int Z_BLOCK = 5;

        /* Allowed flush values; see deflate() and inflate() below for details */

        public static readonly int Z_OK = 0;
        public static readonly int Z_STREAM_END = 1;
        public static readonly int Z_NEED_DICT = 2;
        public static readonly int Z_ERRNO = (-1);
        public static readonly int Z_STREAM_ERROR = (-2);
        public static readonly int Z_DATA_ERROR = (-3);
        public static readonly int Z_MEM_ERROR = (-4);
        public static readonly int Z_BUF_ERROR = (-5);
        public static readonly int Z_VERSION_ERROR = (-6);

        /* 
         * Return codes for the compression/decompression functions. Negative
         * values are errors, positive values are used for special but normal events.
         */
        public static readonly int Z_NO_COMPRESSION = 0;
        public static readonly int Z_BEST_SPEED = 1;
        public static readonly int Z_BEST_COMPRESSION = 9;
        public static readonly int Z_DEFAULT_COMPRESSION = (-1);

        #endregion

        #region z_stream Data Structure
        // ==================================================================
        // z_stream Data Structure
        // ==================================================================
        [DllImport("zlib1.dll")]
        private static extern byte* zlibVersion();

        #endregion

        #region High Level Zip Functions
        // ==================================================================
        // High Level Zip functions
        // ==================================================================

        [DllImport("zlib1.dll")]
        public static extern int compress(byte* dest, int* destLen, byte* source, int sourceLen);

        [DllImport("zlib1.dll")]
        public static extern int compress2(byte* dest, int* destLen, byte* source, int sourceLen, int level);

        [DllImport("zlib1.dll")]
        public static extern long compressBound(int sourceLen);

        [DllImport("zlib1.dll")]
        public static extern int uncompress(byte* dest, int* destLen, byte* source, int sourceLen);

        #endregion

        #region Low Level Zip functions: deflate (zip)
        // ==================================================================
        // Low Level Zip functions : deflate
        // ==================================================================

        public static int deflateInit(z_stream* strm, int level)
        {
            return deflateInit_(strm, level, zlibVersion(), sizeof(z_stream));
        }

        [DllImport("zlib1.dll")]
        private static extern int deflateInit_(z_stream* strm, int level, byte* version, int stream_size);

        [DllImport("zlib1.dll")]
        public static extern int deflate(z_stream* strm, int flush);

        [DllImport("zlib1.dll")]
        public static extern int deflateEnd(z_stream* strm);

        [DllImport("zlib1.dll")]
        public static extern int deflateReset(z_stream* strm);

        #endregion

        #region Low Level Zip Functions : inflate (unzip)
        // ==================================================================
        // Low Level Zip functions : inflate
        // ==================================================================

        public static int inflateInit(z_stream* strm)
        {
            return inflateInit_(strm, zlibVersion(), sizeof(z_stream));
        }

        [DllImport("zlib1.dll")]
        private static extern int inflateInit_(z_stream* strm, byte* version, int stream_size);

        [DllImport("zlib1.dll")]
        public static extern int inflate(z_stream* strm, int flush);

        [DllImport("zlib1.dll")]
        public static extern int inflateEnd(z_stream* strm);

        [DllImport("zlib1.dll")]
        public static extern int inflateReset(z_stream* strm);

        #endregion
    }

    // ======================================================================
    // zlibNative
    // ======================================================================

    /// <summary>
    /// La classe serve per zippare/unzippare dei buffer.
    /// Se deve comprimere o decomprimere deve essere deciso in fase di creazione
    /// dell'istanza.
    /// 
    /// In particolare, in fase di compressione si deve decidere il livello di 
    /// compressione. Possibili valori sono 0..9 e -1 che corrispondena 6.
    /// </summary>
    public class zlibClassNative /*: IDisposable*/
    {
        #region Private Data
        // ==================================================================
        // Private Data
        // ==================================================================

        private z_stream _strm;

        private CompressionMode _mode;

        private int _level;

        #endregion

        #region Constructor
        // ==================================================================
        // Constructor
        // ==================================================================

        /// <summary>
        /// Usare per zippare
        /// </summary>
        /// <param name="level">0..9 oppure -1</param>
        public zlibClassNative(int level)
        {
            _mode = CompressionMode.Compress;
            _level = level;
        }

        /// <summary>
        /// Usare per unzippare
        /// </summary>
        public zlibClassNative()
        {
            _mode = CompressionMode.Decompress;
        }

        #endregion

        #region Interface
        // ==================================================================
        // Interface
        // ==================================================================

        public int Compress(byte[] source, int sourceOffset, int sourceLength, byte[] dest, int destOffset)
        {
            Debug.Assert(_mode == CompressionMode.Compress);

            unsafe
            {
                fixed (z_stream* pstrm = &_strm)
                fixed (byte* psource = &source[sourceOffset])
                fixed (byte* pdest = &dest[destOffset])
                {
                    int ret = zlib.deflateInit(pstrm, /*Z_DEFAULT_COMPRESSION*/_level);
                    if (ret != zlib.Z_OK)
                        throw new ApplicationException("Zipper.Compress.<init> failed: code = " + ret);

                    _strm.next_in = psource;
                    _strm.avail_in = sourceLength;
                    _strm.next_out = pdest;
                    _strm.avail_out = dest.Length - destOffset;
                    ret = zlib.deflate(pstrm, zlib.Z_FINISH);

                    int destLength = _strm.total_out;

                    zlib.deflateEnd(pstrm);

                    if (ret != zlib.Z_OK && ret != zlib.Z_STREAM_END)
                        throw new ApplicationException("Zipper.Compress(<buffer>) failed: code = " + ret);

                    return destLength;
                }
            }
        }

        public int Decompress(byte[] source, int sourceOffset, int sourceLength, byte[] dest, int destOffset)
        {
            Debug.Assert(_mode == CompressionMode.Decompress);

            unsafe
            {
                fixed (z_stream* pstrm = &_strm)
                fixed (byte* psource = &source[sourceOffset])
                fixed (byte* pdest = &dest[destOffset])
                {
                    int ret = zlib.inflateInit(pstrm);
                    if (ret != zlib.Z_OK)
                        throw new ApplicationException("Zipper.Decompress(<init>) failed: code = " + ret);

                    _strm.next_in = psource;
                    _strm.avail_in = sourceLength;
                    _strm.next_out = pdest;
                    _strm.avail_out = dest.Length - destOffset;
                    _strm.total_out = 0;
                    ret = zlib.inflate(pstrm, zlib.Z_FINISH);

                    int destLength = _strm.total_out;

                    zlib.inflateEnd(pstrm);

                    if (ret != zlib.Z_OK && ret != zlib.Z_STREAM_END)
                        throw new ApplicationException("Unzipper.Decompress(<buffer>) failed: " + ret);

                    return destLength;
                }
            }
        }

        #endregion
    }

}
